package com.hb.mybatis.interceptor;

import com.hb.annotation.FieldEncrypt;
import com.hb.encryptor.IEncryptor;
import com.hb.entity.BaseEntity;
import com.hb.util.EncryptedFieldsProvider;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.SqlSessionFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.lang.reflect.Field;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

/**
 * @author hubin
 * @description
 * @createTime 2024/3/10 15:31
 */
@Component
@Intercepts(
        @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})
)
public class MybatisEncryptionPlugin implements Interceptor, ApplicationListener<ContextRefreshedEvent> {

    private static Logger logger = LoggerFactory.getLogger(MybatisEncryptionPlugin.class);

    @Autowired
    private IEncryptor encryptor;

    @Autowired
    private List<SqlSessionFactory> sqlSessionFactories;

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        Object[] args = invocation.getArgs();
        MappedStatement ms = (MappedStatement) args[0];
        if (ms.getSqlCommandType() == SqlCommandType.INSERT || ms.getSqlCommandType() == SqlCommandType.UPDATE) {
            // 1、不使用 @Param 时且参数只有一个时 args[1] 的类型就是这个参数的类型
            // 2、不使用 @Param 时且参数多于一个时 args[1] 的类型是 MapperMethod 类的内部类 ParamMap，ParamMap 继承于 HashMap，这个 Map 里面有四个值。
            //    比如 int insert(Long id, User user); 那么 Map 有四项（key = id, key = user, key = param1, key = param2），
            //    此时是否使用 @Param 都一样
            // 3、使用 @Param 时就算参数只有一个这个 args[1] 的类型是 MapperMethod 类的内部类 ParamMap，ParamMap 继承于 HashMap。
            //    比如 int insert(@Param("user") User user); 那么 Map 有2项（key = user，key = param1）
            Object parameter = args[1];
            Object paramEntity = null;
            if (parameter instanceof Map) {
                Map<String, Object> map = (Map<String, Object>) parameter;
                for (String key : map.keySet()) {
                    Object paramObj = map.get(key);
                    if (paramObj != null && paramObj instanceof BaseEntity) {
                        paramEntity = paramObj;
                        break;
                    }
                }
            } else {
                if (parameter != null && parameter instanceof BaseEntity) {
                    paramEntity = parameter;
                }
            }
            Set<Field> encryptedFields = EncryptedFieldsProvider.get(paramEntity.getClass());
            if (CollectionUtils.isEmpty(encryptedFields)) {
                return invocation.proceed();
            }
            for (Field field : encryptedFields) {
                FieldEncrypt fieldEncrypt = field.getAnnotation(FieldEncrypt.class);
                if (fieldEncrypt == null) {
                    continue;
                }
                if (!field.getType().equals(String.class)) {
                    continue;
                }
                Object originalVal = field.get(paramEntity);
                if (originalVal == null || !StringUtils.hasLength(originalVal.toString())) {
                    continue;
                }
                // 加解密的key必须是16个字符
                String key = "1234567812345678";
                String encryptedValue = encryptor.encrypt(originalVal.toString(), key);
                field.set(paramEntity, encryptedValue);
            }
        }
        return invocation.proceed();
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
    }


    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        for (SqlSessionFactory factory : sqlSessionFactories) {
            factory.getConfiguration().addInterceptor(this);
        }
    }
}
